/*
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

/** \file blender/editors/sculpt_paint/paint_vertex_weight_utils.c
 *  \ingroup edsculpt
 *
 * Intended for use by `paint_vertex.c` & `paint_vertex_weight_ops.c`.
 */

#include "MEM_guardedalloc.h"

#include "BLI_listbase.h"
#include "BLI_math.h"
#include "BLI_string_utils.h"

#include "DNA_armature_types.h"
#include "DNA_mesh_types.h"
#include "DNA_scene_types.h"
#include "DNA_brush_types.h"
#include "DNA_object_types.h"

#include "BKE_action.h"
#include "BKE_context.h"
#include "BKE_deform.h"
#include "BKE_mesh.h"
#include "BKE_modifier.h"
#include "BKE_object_deform.h"
#include "BKE_report.h"

#include "WM_api.h"
#include "WM_types.h"

#include "paint_intern.h"  /* own include */

/* -------------------------------------------------------------------- */
/** \name Weight Paint Sanity Checks
 * \{ */

/* ensure we have data on wpaint start, add if needed */
bool ED_wpaint_ensure_data(
        bContext *C, struct ReportList *reports,
        enum eWPaintFlag flag, struct WPaintVGroupIndex *vgroup_index)
{
	Scene *scene = CTX_data_scene(C);
	Object *ob = CTX_data_active_object(C);
	Mesh *me = BKE_mesh_from_object(ob);

	if (vgroup_index) {
		vgroup_index->active = -1;
		vgroup_index->mirror = -1;
	}

	if (scene->obedit) {
		return false;
	}

	if (me == NULL || me->totpoly == 0) {
		return false;
	}

	/* if nothing was added yet, we make dverts and a vertex deform group */
	if (!me->dvert) {
		BKE_object_defgroup_data_create(&me->id);
		WM_event_add_notifier(C, NC_GEOM | ND_DATA, me);
	}

	/* this happens on a Bone select, when no vgroup existed yet */
	if (ob->actdef <= 0) {
		Object *modob;
		if ((modob = modifiers_isDeformedByArmature(ob))) {
			Bone *actbone = ((bArmature *)modob->data)->act_bone;
			if (actbone) {
				bPoseChannel *pchan = BKE_pose_channel_find_name(modob->pose, actbone->name);

				if (pchan) {
					bDeformGroup *dg = defgroup_find_name(ob, pchan->name);
					if (dg == NULL) {
						dg = BKE_object_defgroup_add_name(ob, pchan->name);  /* sets actdef */
					}
					else {
						int actdef = 1 + BLI_findindex(&ob->defbase, dg);
						BLI_assert(actdef >= 0);
						ob->actdef = actdef;
					}
				}
			}
		}
	}
	if (BLI_listbase_is_empty(&ob->defbase)) {
		BKE_object_defgroup_add(ob);
	}

	/* ensure we don't try paint onto an invalid group */
	if (ob->actdef <= 0) {
		BKE_report(reports, RPT_WARNING, "No active vertex group for painting, aborting");
		return false;
	}

	if (vgroup_index) {
		vgroup_index->active = ob->actdef - 1;
	}

	if (flag & WPAINT_ENSURE_MIRROR) {
		if (me->editflag & ME_EDIT_MIRROR_X) {
			int mirror = ED_wpaint_mirror_vgroup_ensure(ob, ob->actdef - 1);
			if (vgroup_index) {
				vgroup_index->mirror = mirror;
			}
		}
	}

	return true;
}
/** \} */

/* mirror_vgroup is set to -1 when invalid */
int ED_wpaint_mirror_vgroup_ensure(Object *ob, const int vgroup_active)
{
	bDeformGroup *defgroup = BLI_findlink(&ob->defbase, vgroup_active);

	if (defgroup) {
		int mirrdef;
		char name_flip[MAXBONENAME];

		BLI_string_flip_side_name(name_flip, defgroup->name, false, sizeof(name_flip));
		mirrdef = defgroup_name_index(ob, name_flip);
		if (mirrdef == -1) {
			if (BKE_defgroup_new(ob, name_flip)) {
				mirrdef = BLI_listbase_count(&ob->defbase) - 1;
			}
		}

		/* curdef should never be NULL unless this is
		 * a  lamp and BKE_object_defgroup_add_name fails */
		return mirrdef;
	}

	return -1;
}

/* -------------------------------------------------------------------- */
/** \name Weight Blending Modes
 * \{ */

BLI_INLINE float wval_blend(const float weight, const float paintval, const float alpha)
{
	const float talpha = min_ff(alpha, 1.0f);  /* blending with values over 1 doesn't make sense */
	return (paintval * talpha) + (weight * (1.0f - talpha));
}
BLI_INLINE float wval_add(const float weight, const float paintval, const float alpha)
{
	return weight + (paintval * alpha);
}
BLI_INLINE float wval_sub(const float weight, const float paintval, const float alpha)
{
	return weight - (paintval * alpha);
}
BLI_INLINE float wval_mul(const float weight, const float paintval, const float alpha)
{   /* first mul, then blend the fac */
	return ((1.0f - alpha) + (alpha * paintval)) * weight;
}
BLI_INLINE float wval_lighten(const float weight, const float paintval, const float alpha)
{
	return (weight < paintval) ? wval_blend(weight, paintval, alpha) : weight;
}
BLI_INLINE float wval_darken(const float weight, const float paintval, const float alpha)
{
	return (weight > paintval) ? wval_blend(weight, paintval, alpha) : weight;
}

/* mainly for color */
BLI_INLINE float wval_colordodge(float weight, float paintval, float fac)
{
	float mfac, temp;
	if (fac == 0.0f) {
		return weight;
	}
	mfac = 1.0f - fac;
	temp = (paintval == 1.0f) ? 1.0f : min_ff((weight * (225.0f / 255.0f)) / (1.0f - paintval), 1.0f);
	return mfac * weight + temp * fac;
}
BLI_INLINE float wval_difference(float weight, float paintval, float fac)
{
	float mfac, temp;
	if (fac == 0.0f) {
		return weight;
	}
	mfac = 1.0f - fac;
	temp = fabsf(weight - paintval);
	return mfac * weight + temp * fac;
}
BLI_INLINE float wval_screen(float weight, float paintval, float fac)
{
	float mfac, temp;
	if (fac == 0.0f) {
		return weight;
	}
	mfac = 1.0f - fac;
	temp = max_ff(1.0f - (((1.0f - weight) * (1.0f - paintval))), 0);
	return mfac * weight + temp * fac;
}
BLI_INLINE float wval_hardlight(float weight, float paintval, float fac)
{
	float mfac, temp;
	if (fac == 0.0f) {
		return weight;
	}
	mfac = 1.0f - fac;
	if (paintval > 0.5f) {
		temp = 1.0f - ((1.0f - 2.0f * (paintval - 0.5f)) * (1.0f - weight));
	}
	else {
		temp = (2.0f * paintval * weight);
	}
	return mfac * weight + temp * fac;
}
BLI_INLINE float wval_overlay(float weight, float paintval, float fac)
{
	float mfac, temp;
	if (fac == 0.0f) {
		return weight;
	}
	mfac = 1.0f - fac;
	if (weight > 0.5f) {
		temp = 1.0f - ((1.0f - 2.0f * (weight - 0.5f)) * (1.0f - paintval));
	}
	else {
		temp = (2.0f * paintval * weight);
	}
	return mfac * weight + temp * fac;
}
BLI_INLINE float wval_softlight(float weight, float paintval, float fac)
{
	float mfac, temp;
	if (fac == 0.0f) {
		return weight;
	}
	mfac = 1.0f - fac;
	if (weight < 0.5f) {
		temp = ((2.0f * ((paintval / 2.0f) + 0.25f)) * weight);
	}
	else {
		temp = 1.0f - (2.0f * (1.0f - ((paintval / 2.0f) + 0.25f)) * (1.0f - weight));
	}
	return temp * fac + weight * mfac;
}
BLI_INLINE float wval_exclusion(float weight, float paintval, float fac)
{
	float mfac, temp;
	if (fac == 0.0f) {
		return weight;
	}
	mfac = 1.0f - fac;
	temp = 0.5f - ((2.0f * (weight - 0.5f) * (paintval - 0.5f)));
	return temp * fac + weight * mfac;
}

/* vpaint has 'vpaint_blend_tool' */
/* result is not clamped from [0-1] */
float ED_wpaint_blend_tool(
        const int tool,
        /* dw->weight */
        const float weight,
        const float paintval, const float alpha)
{
	switch (tool) {
		case PAINT_BLEND_MIX:
		case PAINT_BLEND_AVERAGE:
		case PAINT_BLEND_SMEAR:
		case PAINT_BLEND_BLUR:       return wval_blend(weight, paintval, alpha);
		case PAINT_BLEND_ADD:        return wval_add(weight, paintval, alpha);
		case PAINT_BLEND_SUB:        return wval_sub(weight, paintval, alpha);
		case PAINT_BLEND_MUL:        return wval_mul(weight, paintval, alpha);
		case PAINT_BLEND_LIGHTEN:    return wval_lighten(weight, paintval, alpha);
		case PAINT_BLEND_DARKEN:     return wval_darken(weight, paintval, alpha);
		/* Mostly make sense for color: support anyway. */
		case PAINT_BLEND_COLORDODGE: return wval_colordodge(weight, paintval, alpha);
		case PAINT_BLEND_DIFFERENCE: return wval_difference(weight, paintval, alpha);
		case PAINT_BLEND_SCREEN:     return wval_screen(weight, paintval, alpha);
		case PAINT_BLEND_HARDLIGHT:  return wval_hardlight(weight, paintval, alpha);
		case PAINT_BLEND_OVERLAY:    return wval_overlay(weight, paintval, alpha);
		case PAINT_BLEND_SOFTLIGHT:  return wval_softlight(weight, paintval, alpha);
		case PAINT_BLEND_EXCLUSION:  return wval_exclusion(weight, paintval, alpha);
		/* Only for color: just use blend. */
		case PAINT_BLEND_LUMINOCITY:
		case PAINT_BLEND_SATURATION:
		case PAINT_BLEND_HUE:
		case PAINT_BLEND_ALPHA_SUB:
		case PAINT_BLEND_ALPHA_ADD:
		default:                     return wval_blend(weight, paintval, alpha);
	}
}

/** \} */
